﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Threading;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.IO;
using System.Drawing;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace collabr8
{
    // This delegate is needed to specify the parameters we're passing with our event
    public delegate void StatusChangedEventHandler(object sender, StatusChangedEventArgs e);
    public delegate void FrameEncodedEventHandler();
    public delegate void EndScreenSharingHandler();
    public delegate void ClipReceivedEventHandler(object sender, ClipReceivedEventArgs e);

    public sealed class Server
    {
        private IPAddress ipAddress;
        private String serverPassword;
        private int serverPort;
        private Session currentSession;
        private TcpClient tcpServer, tcpVideo, tcpClip;
        // The thread that will hold the connection listener
        private Thread thrListener, thrLstVideo, thrLstClip;
        //private Thread thrSendVideo, thrSendFiles;
        // The TCP objects that listens for connections
        private TcpListener tlsClient, tlsVideo, tlsClip;
        public event StatusChangedEventHandler StatusChanged;
        public event FrameEncodedEventHandler FrameEncoded;
        public event ClipReceivedEventHandler ClipReceived;
        public event EndScreenSharingHandler SharingEnded;
        private ClipReceivedEventArgs cre;
        private StatusChangedEventArgs e;
        // Bool variable which will tell the while loop to keep monitoring for connections
        private bool ServRunning = false;
        // Position and dimensions of the captured area
        private int x, y, w, h, width, height;
        private IntPtr window;
        private bool usingWindow = false;
        private Point mousePos;
        private Rectangle area;
        private Bitmap bitmap;
        private Frame currentFrame;
        private Clip outgoingClip;
        private string clipFolder = "clips";
        // VideoCodec object used for encoding
        private VideoCodec encoder;
        // Timer user for screen capture
        private Timer timer, videoWait;
        private BinaryFormatter formatter, videoFormatter, clipFormatter;
        private Message outgoing;
        private TimerCallback timerCallback;
        private bool sharingScreen;
        private static readonly Server instance = new Server();
        private Server() { }

        public static Server Instance
        {
            get 
            {
                return instance; 
            }
        }
        // The constructor sets the IP address to the one retrieved by the instantiating object
        public void InitServer(IPAddress address, String pwd, int port)
        {
            this.sharingScreen = false;
            this.ipAddress = address;
            this.serverPassword = pwd;
            this.serverPort = port;
            currentSession = Session.Current;
        }

        public String getPassword()
        {
            return this.serverPassword;
        }

        // Add the user to the hash tables
        public void AddUser(TcpClient tcpUser, string strUsername)
        {
            // Add the username and associated connection to both hash tables
            currentSession.AddConnection(tcpUser, strUsername);
            // Tell of the new connection to all other users and to the server form
            SendAdminMessage(currentSession.UserByConnection(tcpUser) + " has joined us");
        }

        public void AddConnection(Connection c)
        {
            currentSession.AddConnectionHandler(c);
        }

        // Remove the user from the hash tables
        public void RemoveUser(TcpClient tcpUser)
        {
            // If the user is there
            if (currentSession.HasUser(tcpUser))
            {
                String user = currentSession.UserByConnection(tcpUser);
                // Remove the user from the hash table
                currentSession.RemoveConnection(tcpUser);
                // Then show the information and tell the other users about the disconnection
                SendAdminMessage(user + " has left us");
            }
        }

        public void RemoveUser(String user)
        {
            // If the user is there
            if (currentSession.HasUser(user))
            {
                // Remove the user from the hash table 
                currentSession.RemoveConnection(user);
                // Then show the information and tell the other users about the disconnection
                SendAdminMessage(user + " has left us");
            }
        }

        public void RemoveClipUser(TcpClient tcpUser)
        {
            // If the user is there
            if (currentSession.HasClipUser(tcpUser))
            {
                currentSession.RemoveClipConnection(tcpUser);
            }
        }


        // This is called when we want to raise the StatusChanged event
        public void OnStatusChanged(StatusChangedEventArgs e)
        {
            StatusChangedEventHandler statusHandler = this.StatusChanged;
            if (statusHandler != null)
            {
                // Invoke the delegate
                statusHandler(null, e);
            }
        }

        // This is called when we want to raise the FrameEncoded event
         public void OnFrameEncoded()
        {
            //FrameEncodedEventHandler frameHandler = this.FrameEncoded;
            if (this.FrameEncoded != null)
            {
                // Invoke the delegate
                this.FrameEncoded();
            }
        }

        // This is called when we want to raise the ClipReceived event
        public void OnClipReceived(ClipReceivedEventArgs cre)
        {
            //ClipReceivedEventHandler clipHandler = this.ClipReceived;
            if (this.ClipReceived != null)
            {
                // Invoke the delegate
                this.ClipReceived(null, cre);
            }
        }

        public void OnSharingEnded()
        {
            //ClipReceivedEventHandler clipHandler = this.ClipReceived;
            if (this.SharingEnded != null)
            {
                // Invoke the delegate
                this.SharingEnded();
            }
        }


        // Send administrative messages
        public void SendAdminMessage(string Message)
        {
            // First of all, show in our application who says what
            e = new StatusChangedEventArgs("Administrator: " + Message);
            OnStatusChanged(e);

            this.outgoing = new Message("Administrator: " + Message);
            // Get TcpClients:
            TcpClient[] tcpClients = currentSession.Clients;
            // Loop through the list of TCP clients
            for (int i = 0; i < tcpClients.Length; i++)
            {
                // Try sending a message to each
                try
                {
                    // If the message is blank or the connection is null, break out
                    if (Message.Trim() == "" || tcpClients[i] == null)
                    {
                        continue;
                    }
                    // Send the message to the current user in the loop

                    this.formatter.Serialize(tcpClients[i].GetStream(), this.outgoing);
                }
                catch // If there was a problem, the user is not there anymore, remove him
                {
                    currentSession.RemoveConnection(tcpClients[i]);
                }
            }
        }

        // Send messages from one user to all the others

        public void SendFrame()
        {
            // Get TcpClients:
            TcpClient[] tcpClients = currentSession.VideoClients;
            // Loop through the list of TCP clients
            for (int i = 0; i < tcpClients.Length; i++)
            {
                // Try sending the Frame to each
                try
                {

                    // Send the message to the current user in the loop

                    this.videoFormatter.Serialize(tcpClients[i].GetStream(), this.currentFrame);
                }
                catch // If there was a problem, the user is not there anymore, remove him
                {
                    currentSession.RemoveVideoConnection(tcpClients[i]);
                }
            }
            if (currentSession.VideoCount == 0)
            {
                this.sharingScreen = false;
                OnSharingEnded();
            }    
        }

        public void SendClip(string fname)
        {
            if (this.outgoingClip != null)
                this.outgoingClip.Dispose();
            this.outgoingClip = new Clip(clipFolder + "\\" + fname, fname);

            // Get TcpClients:
            TcpClient[] tcpClients = currentSession.ClipClients;
            // Loop through the list of TCP clients
            for (int i = 0; i < tcpClients.Length; i++)
            {
                // Try sending the Frame to each
                try
                {
                    // If the message is blank or the connection is null, break out
                    if (tcpClients[i] == null)
                    {
                        continue;
                    }
                    // Send the message to the current user in the loop
                    
                    this.clipFormatter.Serialize(tcpClients[i].GetStream(), this.outgoingClip);
                }
                catch // If there was a problem, the user is not there anymore, remove him
                {
                    currentSession.RemoveClipConnection(tcpClients[i]);
                }
            }
        }

        public void SendClip(Clip recClip)
        {
            cre = new ClipReceivedEventArgs(recClip.Name);
            OnClipReceived(cre);
            // Get TcpClients:
            TcpClient[] tcpClients = currentSession.ClipClients;
            // Loop through the list of TCP clients
            for (int i = 0; i < tcpClients.Length; i++)
            {
                // Try sending the Frame to each
                try
                {
                    // If the message is blank or the connection is null, break out
                    if (tcpClients[i] == null)
                    {
                        continue;
                    }
                    // Send the message to the current user in the loop
                   
                    this.clipFormatter.Serialize(tcpClients[i].GetStream(), recClip);
                }
                catch // If there was a problem, the user is not there anymore, remove him
                {
                    currentSession.RemoveClipConnection(tcpClients[i]);
                }
            }
        }


        public void SendMessage(string From, string Message)
        {
            // First of all, show in our application who says what
            e = new StatusChangedEventArgs(From + " says: " + Message);
            OnStatusChanged(e);

            this.outgoing = new Message(From + " says: " + Message);
            // Get TcpClients:
            TcpClient[] tcpClients = currentSession.Clients;
            // Loop through the list of TCP clients
            for (int i = 0; i < tcpClients.Length; i++)
            {
                // Try sending a message to each
                try
                {
                    // If the message is blank or the connection is null, break out
                    if (Message.Trim() == "" || tcpClients[i] == null)
                    {
                        continue;
                    }
                    // Send the message to the current user in the loop

                    this.formatter.Serialize(tcpClients[i].GetStream(), this.outgoing);
                }
                catch // If there was a problem, the user is not there anymore, remove him
                {
                    currentSession.RemoveConnection(tcpClients[i]);
                }
            }
        }

        public bool IsConnected(String usrName)
        {
            return currentSession.HasUser(usrName);
        }

        public void StartListening()
        {

            // Get the IP of the first network device, however this can prove unreliable on certain configurations
            IPAddress ipaLocal = ipAddress;

            // Create the TCP listener object using the IP of the server and the specified port
            tlsClient = new TcpListener(serverPort);
            tlsVideo = new TcpListener(serverPort + 1);
            tlsClip = new TcpListener(serverPort + 2);

            // Start the TCP listener and listen for connections
            tlsClient.Start();
            tlsVideo.Start();
            tlsClip.Start();
            // tlsFiles.Start();

            // The while loop will check for true in this before checking for connections
            ServRunning = true;

            this.formatter = new BinaryFormatter();
            this.videoFormatter = new BinaryFormatter();
            this.clipFormatter = new BinaryFormatter();

            // Start the new tread that hosts the listener
            thrListener = new Thread(KeepListening);
            thrLstVideo = new Thread(ListenForVideo);
            thrLstClip = new Thread(ListenForClip);
            thrListener.IsBackground = true;
            thrLstVideo.IsBackground = true;
            thrLstClip.IsBackground = true;
            thrListener.Start();
            thrLstVideo.Start();
            thrLstClip.Start();
        }

        public void KeepListening()
        {
            // While the server is running
            while (ServRunning == true)
            {
                // Accept a pending connection
                tcpServer = tlsClient.AcceptTcpClient();

                // Create a new instance of Connection
                Connection newConnection = new Connection(tcpServer);
            }
        }

        public void ListenForVideo()
        {
            // While the server is running
            while (ServRunning == true)
            {
                // Accept a pending connection
                tcpVideo = tlsVideo.AcceptTcpClient();
                currentSession.AddVideoConnection(tcpVideo);
            }
        }

        public void ListenForClip()
        {
            // While the server is running
            while (ServRunning == true)
            {
                // Accept a pending connection
                tcpClip = tlsClip.AcceptTcpClient();
                currentSession.AddClipConnection(tcpClip);
            }
        }

        [DllImport("user32.dll")]
        private static extern int GetWindowRect(IntPtr hwnd, out Rectangle rect);

        public void startVideo(IntPtr handle)
        {
            this.window = handle;
            this.usingWindow = true;
            this.area = new Rectangle();
            GetWindowRect(this.window, out this.area);
            this.x = this.area.Left;
            this.y = this.area.Top;
            this.width = this.area.Width - this.area.Left + 6;
            this.height = this.area.Height - this.area.Top + 23;
            // Let's send VideoOn messages to clients:
            Message m = new Message(MessageCode.VideoOn, (this.width.ToString() + ";" + this.height.ToString()));
            TcpClient[] tcpClients = currentSession.Clients;
            // Loop through the list of TCP clients
            for (int i = 0; i < tcpClients.Length; i++)
            {
                // Try sending a message to each
                try
                {
                    // If the message is blank or the connection is null, break out
                    if (tcpClients[i] == null)
                    {
                        continue;
                    }
                    // Send the message to the current user in the loop

                    this.formatter.Serialize(tcpClients[i].GetStream(), m);
                }
                catch // If there was a problem, the user is not there anymore, remove him
                {
                    currentSession.RemoveConnection(tcpClients[i]);
                }
            }

            while (currentSession.Count != currentSession.VideoCount)
            {
                this.sharingScreen = false;
            }

            this.startScreenSharing();
        }

        public void startVideo(int x, int y, int width, int height)
        {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
            this.usingWindow = false;
            this.area = new Rectangle(x, y, width, height);
            // Let's send VideoOn messages to clients:
            Message m = new Message(MessageCode.VideoOn, (this.width.ToString() + ";" + this.height.ToString()) );
            TcpClient[] tcpClients = currentSession.Clients;
            // Loop through the list of TCP clients
            for (int i = 0; i < tcpClients.Length; i++)
            {
                // Try sending a message to each
                try
                {
                    // If the message is blank or the connection is null, break out
                    if (tcpClients[i] == null)
                    {
                        continue;
                    }
                    // Send the message to the current user in the loop

                    this.formatter.Serialize(tcpClients[i].GetStream(), m);
                }
                catch // If there was a problem, the user is not there anymore, remove him
                {
                    currentSession.RemoveConnection(tcpClients[i]);
                }
            }

            while (currentSession.Count != currentSession.VideoCount)
            {
                this.sharingScreen = false;
            }

            this.startScreenSharing();
        }

        public void StopVideo()
        {
            this.sharingScreen = false;
            this.currentSession.ClearVideoConnections();  
        }

        public void startScreenSharing()
        {
            if (!this.sharingScreen && currentSession.VideoCount!=0)
            {
                this.sharingScreen = true;
                this.FrameEncoded = new FrameEncodedEventHandler(this.SendFrame);
                this.bitmap = new Bitmap(width, height);
                this.encoder = new VideoCodec();
                this.timerCallback = new TimerCallback(this.OnTimerFire);
                this.timer = new Timer(this.timerCallback, null, 0, Timeout.Infinite);
            }
        }

        public void StopListening()
        {
            if (ServRunning)
            {
                currentSession.End();
                ServRunning = false;
                sharingScreen = false;
            }
        }


        private void OnTimerFire(Object o)
        {
            if (this.sharingScreen && currentSession.VideoCount != 0)
            {
                if (this.usingWindow)
                {
                    GetWindowRect(this.window, out this.area);
                    this.x = this.area.Left;
                    this.y = this.area.Top;
                    this.w = this.area.Width - this.area.Left + 6;
                    this.h = this.area.Height - this.area.Top + 23;
                    if (w != width || h != height)
                    {
                        this.width = w;
                        this.height = h;
                        this.encoder.Reset();
                        if (this.bitmap != null)
                            this.bitmap.Dispose();
                        this.bitmap = new Bitmap(this.width, this.height);
                    }
                }

                this.CaptureScreen();
                if (this.currentFrame != null)
                    this.currentFrame.Dispose();
                this.currentFrame = this.encoder.Encode(this.bitmap);
                this.mousePos = System.Windows.Forms.Cursor.Position;
                if (this.area.Contains(this.mousePos))
                {
                    currentFrame.setMouseCoords(mousePos.X - this.x, mousePos.Y - this.y);
                }
                OnFrameEncoded();

                this.timer.Change(0, Timeout.Infinite);
            }
            else if (currentSession.VideoCount == 0)
            {
                this.StopVideo();
            }
        }

        private void CaptureScreen()
        {
            using (Graphics g = Graphics.FromImage(this.bitmap))
            {
                g.CopyFromScreen(this.x, this.y, 0, 0, new Size(this.width, this.height));
            }
        }

        public void ChangeVideo(int x, int y, int w, int h)
        {
            this.usingWindow = false;
            this.encoder.Reset();
            this.x = x;
            this.y = y;
            this.area = new Rectangle(x, y, w, h);
            if (this.width != w || this.height != h)
            {
                    this.bitmap.Dispose();
                    this.bitmap = new Bitmap(w, h);
            }

            this.width = w;
            this.height = h;
        }

        public void ChangeVideo(IntPtr win)
        {
            this.usingWindow = true;
            this.window = win;
            this.encoder.Reset();
        }

        public Bitmap CurrentFrame
        {
            get
            {
                return this.bitmap;
            }
        }

        public bool IsSharingScreen
        {
            get
            {
                return this.sharingScreen;
            }
        }

        public bool IsRunning
        {
            get
            {
                return this.ServRunning;
            }
        }

    }
}
